<script lang="ts">
export default {
  name: "xt-upload",
};
</script>

<script setup lang="ts">
import useRefHook from "../../../hooks/ref";
import { ref, watch } from "vue";
import { ElUpload } from "element-plus";
import axios from "axios";
import { $singleMsg } from "../../../utils/element";
import { formatTimestamp, getUuid } from "../../../utils/utils";
import xtButton from "../../xt-button/src/main.vue";
import xtProgress from "../../xt-progress/src/main.vue";
import Typings from "../../../typings";

const { getInstance, INSTANCE } = useRefHook();

interface Props {
  [propName: string]: any;
  action?: Typings.Upload.props["action"];
  accept?: Typings.Upload.props["accept"];
  groupId?: string;
  limit?: Typings.Upload.props["limit"];
  fileSize?: number;
  disabled?: Typings.Upload.props["disabled"];
  drag?: Typings.Upload.props["drag"];
}

const props = withDefaults(defineProps<Props>(), {
  action: "/file/upload", // 默认的文件上传地址
  accept: ".png,.jpeg,.jpg,.gif,.pdf,.doc,.docx,.xlsx,.xls", // 默认允许的上传文件类型
  groupId: "", // 文件组ID
  limit: 5, // 最大上传个数默认为5个
  fileSize: 1024 * 1024, // 1MB
  disabled: false, // 默认不置灰
  drag: false,
});

// 组件自定义钩子
const emit = defineEmits([
  "success", // 文件上传成功回调
  "remove", // 文件删除成功回调
  "exceed", // 上传数量超出 limit 回调
  "update:fileList", // 更新外部 fileList
  "update:groupId", // 更新外部 groupId
]);

// 是否置灰上传
const uploadLoading = ref(0);

// 是否阻止监听 groupId，排除第一次上传成功的情况
const shouldStopWatchGroupId = ref<boolean>(false);
watch(
  // groupId 从无到有时，请求接口获取对应的文件组文件(第一次上传成功的除外)
  () => props.groupId,
  async (n, o) => {
    if (n && !o && !shouldStopWatchGroupId.value) {
      // 获取最新的文件组文件
      getGroupIdFiles(n);
    }
  }
);

// 根据 groupId 获取最新的文件
const getGroupIdFiles = async (groupId: string) => {
  try {
    const params = {
      groupId,
    };
    uploadLoading.value++;
    const res = await axios.get("/file/getFileByGroupId", {
      params,
    });
    if (res.data.code === 0) {
      emit("update:fileList", res.data.data);
    } else {
      $singleMsg({
        type: "error",
        message: res.data.msg,
      });
    }
  } finally {
    uploadLoading.value--;
  }
};

// 上传文件成功回调
const onSuccess = (response, file, fileList) => {
  shouldStopWatchGroupId.value = true;

  uploadLoading.value--;
  if (response.code !== 0) {
    return $singleMsg({ type: "error", message: response.msg });
  }

  const list = fileList.map((i) => {
    if (!i.response) {
      return i;
    } else {
      return i.response.data[0];
    }
  });

  const _file = file.response.data[0];
  emit("success", response, _file, list);
  emit("update:fileList", list);
  emit("update:groupId", _file.groupId);
};

// 删除文件成功回调
const onRemove = (file, fileList) => {
  emit("update:fileList", fileList);
  if (!fileList.length) {
    emit("update:groupId", "");
  }
  emit("remove", file, fileList);
};

// 文件上传数量超出规定值时
const onExceed = (files, uploadFiles) => {
  $singleMsg({
    type: "warning",
    message: `上传文件数量不能超过 ${props.limit} 个`,
  });
  emit("exceed", files, uploadFiles);
};

// 上传前拦截处理
const beforeUpload = (rawFile) => {
  uploadLoading.value++;
  const { name, size } = rawFile;

  // 处理 accept 格式
  const accept = (props.accept || "")
    .toLowerCase()
    .replaceAll(".", "")
    .split(",");

  // 文件后缀名
  const etx = name.split(".").at(-1);

  // 文件格式限制
  if (props.accept && !accept.includes(etx.toLowerCase())) {
    $singleMsg({
      type: "warning",
      message: `当前上传文件格式不符合，上传格式仅支持 ${props.accept}`,
    });
    uploadLoading.value--;
    return false;
  }

  // 文件大小限制
  if (props.fileSize && size > props.fileSize) {
    $singleMsg({
      type: "warning",
      message: `当前上传文件大小超出规定值，上传体积不能大于 ${Math.round(
        props.fileSize / 1024 / 1024
      )}MB`,
    });
    uploadLoading.value--;
    return false;
  }
};

// 下载文件
const downloadFile = (file) => {
  window.open(file.url, "_blank");
};

// 删除文件并获取文件组的最新文件列表
const deleteFile = async (file) => {
  try {
    file.loading = true;
    const params = {
      groupId: file.groupId,
      fileId: file.fileId,
    };
    const res = await axios.get("/file/del", {
      params,
    });
    if (res.data.code === 0) {
      $singleMsg({
        type: "success",
        message: "删除成功",
      });

      INSTANCE.value.handleRemove(file);
    } else {
      $singleMsg({
        type: "error",
        message: res.data.msg,
      });
    }
  } finally {
    file.loading = false;
  }
};

defineExpose({
  getInstance,
});
</script>

<template>
  <el-upload
    class="xt-upload"
    ref="INSTANCE"
    :action="action"
    :accept="accept"
    :limit="limit"
    :disabled="disabled || !!uploadLoading"
    :drag="drag"
    :data="{ groupId: groupId || getUuid() }"
    :on-success="onSuccess"
    :on-remove="onRemove"
    :on-exceed="onExceed"
    :before-upload="beforeUpload"
  >
    <slot>
      <xt-button
        v-if="typeof drag === 'boolean' && !drag"
        :disabled="disabled"
        :loading="!!uploadLoading"
        >上传文件</xt-button
      >
      <p v-else class="drag-tip">将文件拖拽到此处</p>
    </slot>

    <template #tip>
      <slot name="tip">
        <div class="el-upload__tip">
          <p v-if="accept">上传文件仅支持 {{ accept }} 格式</p>
          <p>文件大小不能大于 {{ (fileSize / 1024 / 1024).toFixed(2) }}MB.</p>
        </div>
      </slot>
    </template>

    <template #file="{ file }">
      <div class="file-list-container">
        <!-- 文件上传中，展示进度条 -->
        <div class="uploading-container" v-if="file.status === 'uploading'">
          <p>{{ file.name }}</p>
          <xt-progress :percentage="file.percentage" :stroke-width="2" />
        </div>

        <!-- 文件上传成功后，展示上传时间，删除按钮等 -->
        <div class="uploaded-container" v-if="file.url">
          <p class="left">
            <span @click="downloadFile(file)" class="name">{{
              file.name
            }}</span>
          </p>
          <p class="right">
            <span>{{ formatTimestamp((file as any).createdTime) }}</span>
            <xt-button
              type="text"
              :loading="(file as any).loading"
              @click="deleteFile(file)"
              >删除</xt-button
            >
          </p>
        </div>
      </div>
    </template>
  </el-upload>
</template>

<style lang="scss">
@use "../../../scss/xt-upload.scss";
</style>
